Categories
Testing

JavaScript Unit Test Best Practices — Names and Expects

Spread the love

Unit tests are very useful for checking how our app is working.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices we should follow when writing JavaScript unit tests.

Name Our Tests Properly

We should have concise, explicit, and descriptive names in our tests.

This way, we know what we’re testing.

Instead of writing:

describe('employee app', () => {
  it('returns an array', () => {
  });

  // ...
});

We write:

describe('employee app', () => {
  it('returns a list of employees when initialized', () => {
  });

  it('should calculate the pay of an employee when initialized', () => {
  });

// ...
});

We have the unit of work, scenario or content, and the expected behavior.

We can have them in this format:

describe('[unit of work]', () => {
  it('should [expected behaviour] when [scenario/context]', () => {
  });
});

or:

describe('[unit of work]', () => {
  describe('when [scenario/context]', () => {
    it('should [expected behaviour]', () => {
    });
  });
});

So we can write:

describe('employee app', () => {
  describe('when initialized', () => {
    it('returns a list of employees', () => {
    });

    it('should calculate the pay of an employee', () => {
    });
  });

  // ...
});

Don’t Comment Out Tests

We shouldn’t comment out tests.

If they’re too slow or produce false results, then we should fix the test to be faster and more reliable.

Avoid Logic in Our Tests

We should avoid logic in our tests.

This means we should avoid conditionals and loops.

Conditionals can make it take any path.

Loops make our tests share state.

So we shouldn’t write:

it('should get employee by id', () => {
  const employees = [
    { id: 1, name: 'james' },
    { id: 2, name: 'may' },
    { id: 3, name: 'mary' },
    { id: 4, name: 'john' },
    { id: 5, name: 'james' },
  ]

  for (const em of employees) {
    expect(getEmployee(em.id).name).toBe(em.name);
  }
});

Instead, we separate them into their own expect statements:

it('should get employee by id', () => {
  expect(getEmployee(1)).toBe('james');
  expect(getEmployee(2)).toBe('may');
  expect(getEmployee(3)).toBe('mary');
  expect(getEmployee(4)).toBe('john');
  expect(getEmployee(5)).toBe('james');
});

We have a clear output of all the cases.

We can also write out all the cases as their own test:

it('should sanitize a string containing non-ASCII chars', () => {
  expect(sanitizeString(`Avi${String.fromCharCode(243)}n`)).toBe('Avion');
});

it('should sanitize a string containing spaces', () => {
  expect(sanitizeString('foo bar')).toBe('foo-bar');
});

it('should sanitize a string containing exclamation signs', () => {
  expect(sanitizeString('funny chars!!')).toBe('funny-chars-');
});

it('should sanitize a filename containing spaces', () => {
  expect(sanitizeString('file name.zip')).toBe('file-name.zip');
});

it('should sanitize a filename containing more than one dot', () => {
  expect(sanitizeString('my.name.zip')).toBe('my-name.zip');
});

Don’t Write Unnecessary Expectations

We shouldn’t write unnecessary expectations.

If there’s the stuff that’s not used, then we shouldn’t add it.

For example, we can write:

it('compute the number by multiplying and subtracting by 2', () => {
  const multiplySpy = spyOn(Calculator, 'multiple').and.callThrough();
  const subtractSpy = spyOn(Calculator, 'subtract').and.callThrough();

  const result = Calculator.compute(22.5);

  expect(multiplySpy).toHaveBeenCalledWith(22.5, 2);
  expect(subtractSpy).toHaveBeenCalledWith(45, 2);
  expect(result).toBe(43);
});

We don’t need the spies since we’re testing the results of the computation.

So we can just write:

it('compute the number by multiplying and subtracting by 2', () => {
  const result = Calculator.compute(22.5);
  expect(result).toBe(43);
})

We just test the results since that’s what we care about.

We don’t want to check the implementation details.

Conclusion

We should test the results rather than the implementation details.

Also, we should name tests properly.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *